package com.xsjiong.iamath;

public class Interval {
	public static final Class C = Interval.class;

	public static final Interval ZERO = new Interval(0), ONE = new Interval(1), E = new Interval(Math.E), PI = new Interval(Math.PI), NaN = new Interval(Double.NaN),
			INF = new Interval(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);

	public double a, b;

	public Interval() {
		this.a = this.b = 0;
	}

	public Interval(double a) {
		set(a, a);
	}

	public Interval(double a, double b) {
		set(a, b);
	}

	public void set(Interval another) {
		set(another.a, another.b);
	}

	public void set(double a) {
		this.a = this.b = a;
	}

	public void set(double a, double b) {
		if (a > b) {
			this.b = a;
			this.a = b;
		} else {
			this.a = a;
			this.b = b;
		}
	}

	// First max and then min
	public static double[] FourMaxAndMin(double a, double b, double c, double d) {
		double[] m = {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY};
		if (a > m[0]) m[0] = a;
		if (a < m[1]) m[1] = a;
		if (b > m[0]) m[0] = b;
		if (b < m[1]) m[1] = b;
		if (c > m[0]) m[0] = c;
		if (c < m[1]) m[1] = c;
		if (d > m[0]) m[0] = d;
		if (d < m[1]) m[1] = d;
		return m;
	}

	public Interval plus(Interval q) {
		set(a + q.a, b + q.b);
		return this;
	}

	public Interval minus(Interval q) {
		set(a - q.b, b - q.a);
		return this;
	}

	public Interval multiply(Interval q) {
		double[] data = FourMaxAndMin(a * q.a, a * q.b, b * q.a, b * q.b);
		set(data[1], data[0]);
		return this;
	}

	public Interval divide(Interval q) {
		if (q.a==0&&q.b==0) {
			set(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
			return this;
		}
		if (q.a==0) {
			this.a = Math.min(a / q.b, b / q.b);
			this.b = Double.POSITIVE_INFINITY;
			return this;
		}
		if (q.b==0) {
			this.b = Math.max(a / q.a, b / q.a);
			this.a = Double.NEGATIVE_INFINITY;
			return this;
		}
		double[] data = FourMaxAndMin(a / q.a, a / q.b, b / q.a, b / q.b);
		set(data[1], data[0]);
		return this;
	}

	@Override
	public String toString() {
		return "[" + a + ", " + b + ']';
	}

	public Interval CreateClone() {
		return new Interval(a, b);
	}

	public Interval sqrt() {
		set(Math.sqrt(a), Math.sqrt(b));
		return this;
	}

	public Interval sin() {
		if (b - a >= Math.PI * 2) {
			set(-1, 1);
			return this;
		}
		int q1 = (int) Math.floor(a / Math.PI - 0.5), q2 = (int) Math.floor(b / Math.PI - 0.5);
		boolean s1 = q1 % 2 == 0, s2 = q2 % 2 == 0;
		if (s1) {
			if (s2) {
				if (Math.abs(q1 - q2) >= 1)
					set(-1, 1);
				else
					set(Math.sin(a), Math.sin(b));
				return this;
			}
			set(-1, Math.max(Math.sin(a), Math.sin(b)));
		} else {
			if (s2) {
				set(Math.min(Math.sin(a), Math.sin(b)), 1);
				return this;
			}
			if (Math.abs(q1 - q2) >= 1)
				set(-1, 1);
			else
				set(Math.sin(a), Math.sin(b));
		}
		return this;
	}

	public Interval cos() {
		if (b - a >= Math.PI * 2) {
			set(-1, 1);
			return this;
		}
		int q1 = Math.abs((int) Math.floor(a / Math.PI)), q2 = Math.abs((int) Math.floor(b / Math.PI));
		boolean s1 = q1 % 2 == 0, s2 = q2 % 2 == 0;
		if (s1) {
			if (s2) {
				if (Math.abs(q1 - q2) >= 1)
					set(-1, 1);
				else
					set(Math.cos(a), Math.cos(b));
				return this;
			}
			set(-1, Math.max(Math.cos(a), Math.cos(b)));
		} else {
			if (s2) {
				set(Math.min(Math.cos(a), Math.cos(b)), 1);
				return this;
			}
			if (Math.abs(q1 - q2) >= 1)
				set(-1, 1);
			else
				set(Math.cos(a), Math.cos(b));
		}
		return this;
	}

	public Interval tan() {
		if (b - a >= Math.PI * 2) {
			set(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
			return this;
		}
		int q1 = (int) Math.floor(a / Math.PI - 0.5), q2 = (int) Math.floor(b / Math.PI - 0.5);
		if (q1 == q2) {
			set(Math.tan(a), Math.tan(b));
			return this;
		}
		set(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
		return this;
	}

	public Interval cot() {
		if (b - a >= Math.PI * 2) {
			set(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
			return this;
		}
		int q1 = (int) Math.floor(a / Math.PI), q2 = (int) Math.floor(b / Math.PI);
		if (q1 == q2) {
			set(1 / Math.tan(a), 1 / Math.tan(b));
			return this;
		}
		set(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
		return this;
	}

	public Interval abs() {
		if ((a < 0 && b > 0) || (a > 0 && b < 0)) {
			set(0, Math.max(Math.abs(a), Math.abs(b)));
			return this;
		}
		set(Math.abs(a), Math.abs(b));
		return this;
	}

	public Interval negative() {
		set(-b, -a);
		return this;
	}

	public Interval pow(double q) {
		if (q == 0) {
			set(1);
			return this;
		}
		double p1=Math.pow(a, q), p2=Math.pow(b, q);
		if (a>=0||b<0) {
			set(p1,p2);
			return this;
		}
		set(0,Math.max(p1, p2));
		return this;
	}

	public Interval union(Interval q) {
		set(Math.min(a, q.a), Math.max(b, q.b));
		return this;
	}

	public Interval pow(Interval q) {
		double[] data = FourMaxAndMin(Math.pow(a, q.a), Math.pow(a, q.b), Math.pow(b, q.a), Math.pow(b, q.b));
		set(data[1], data[0]);
		return this;
	}

	public boolean isNaN() {
		return Double.isNaN(a) || Double.isNaN(b);
	}

	public boolean contains(double value) {
		return value >= a && value <= b;
	}

	public boolean contains(Interval x) {
		if (a <= x.a) return b >= x.a;
		return x.b >= a;
	}

	public boolean innerContains(Interval x) {
		if (a <= x.a) return b > x.a;
		return x.b > a;
	}

	public boolean innerContains(double value) {
		return value > a && value < b;
	}

	public boolean lessThan(double e) {
		return a < e;
	}

	public boolean greaterThan(double e) {
		return b > e;
	}

	public boolean lessThanOrEqualTo(double e) {
		return a <= e;
	}

	public boolean greaterThanOrEqualTo(double e) {
		return b >= e;
	}

	public Interval remainder(double x) {
		double xd = Math.nextAfter(x, Double.NEGATIVE_INFINITY);
		if (b - a >= x) {
			set(0, xd);
			return this;
		}
		int aa = (int) (a / x), bb = (int) (b / x);
		if (aa == bb) {
			set(a - aa * x, b - bb * x);
			return this;
		}
		set(0, xd);
		return this;
	}

	public Interval remainder(Interval x) {
		remainder(x.a);
		return union(CreateClone().remainder(x.b));
	}

	public Interval plus(double v) {
		a += v;
		b += v;
		return this;
	}

	public Interval minus(double v) {
		a -= v;
		b -= v;
		return this;
	}

	public Interval multiply(double v) {
		set(a * v, b * v);
		return this;
	}

	public Interval divide(double v) {
		set(a / v, b / v);
		return this;
	}

	public double distance() {
		return b - a;
	}
}